﻿using System;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Helpers.Reflection.Emit;
using static BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation.RunnableConstants;

namespace BenchmarkDotNet.Toolchains.InProcess.Emit.Implementation
{
    internal class ByRefConsumeEmitter : ConsumeEmitter
    {
        private FieldBuilder workloadDefaultValueHolderField;
        private MethodInfo overheadKeepAliveWithoutBoxingMethod;
        private MethodInfo workloadKeepAliveWithoutBoxingMethod;
        private LocalBuilder resultLocal;

        public ByRefConsumeEmitter(ConsumableTypeInfo consumableTypeInfo) : base(consumableTypeInfo) { }

        protected override void OnDefineFieldsOverride(TypeBuilder runnableBuilder)
        {
            var nonRefType = ConsumableInfo.WorkloadMethodReturnType.GetElementType();
            if (nonRefType == null)
                throw new InvalidOperationException($"Bug: type {ConsumableInfo.WorkloadMethodReturnType} is non-ref type.");

            workloadDefaultValueHolderField = runnableBuilder.DefineField(
                WorkloadDefaultValueHolderFieldName,
                nonRefType, FieldAttributes.Private);
        }

        protected override void EmitDisassemblyDiagnoserReturnDefaultOverride(ILGenerator ilBuilder)
        {
            /*
                // return ref workloadDefaultValueHolder;
                IL_0031: ldarg.0
                IL_0032: ldflda int32 BenchmarkDotNet.Autogenerated.Runnable_0::workloadDefaultValueHolder
                IL_0037: ret
             */
            ilBuilder.Emit(OpCodes.Ldarg_0);
            ilBuilder.Emit(OpCodes.Ldflda, workloadDefaultValueHolderField);
            ilBuilder.Emit(OpCodes.Ret);
        }

        protected override void OnEmitMembersOverride(TypeBuilder runnableBuilder)
        {
            overheadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods()
                .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing)
                            && m.GetParameterTypes().First().IsByRef == false)
                .MakeGenericMethod(ConsumableInfo.OverheadMethodReturnType);

            workloadKeepAliveWithoutBoxingMethod = typeof(DeadCodeEliminationHelper).GetMethods()
                .First(m => m.Name == nameof(DeadCodeEliminationHelper.KeepAliveWithoutBoxing)
                            && m.GetParameterTypes().First().IsByRef)
                .MakeGenericMethod(ConsumableInfo.WorkloadMethodReturnType.GetElementType());
        }

        protected override void DeclareActionLocalsOverride(ILGenerator ilBuilder)
        {
            /*
                .locals init (
                    [4] native int,
                )
                -or-
                .locals init (
                    [4] int32&,
                )
             */
            if (ActionKind == RunnableActionKind.Overhead)
                resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.OverheadMethodReturnType);
            else
                resultLocal = ilBuilder.DeclareLocal(ConsumableInfo.WorkloadMethodReturnType);
        }

        /// <summary>Emits the action before loop override.</summary>
        /// <param name="ilBuilder">The il builder.</param>
        /// <exception cref="ArgumentOutOfRangeException">EmitActionKind - null</exception>
        protected override void EmitActionBeforeLoopOverride(ILGenerator ilBuilder)
        {
            /*
                // IntPtr value = default(IntPtr);
                IL_001c: ldloca.s 4
                IL_001e: initobj [mscorlib]System.IntPtr
                -or-
                // ref int reference = ref workloadDefaultValueHolder;
                IL_001c: ldarg.0
                IL_001d: ldflda int32 BenchmarkDotNet.Autogenerated.Runnable_0::workloadDefaultValueHolder
                IL_0022: stloc.s 4
             */
            if (ActionKind == RunnableActionKind.Overhead)
            {
                ilBuilder.EmitLdloca(resultLocal);
                ilBuilder.Emit(OpCodes.Initobj, ConsumableInfo.OverheadMethodReturnType);
            }
            else
            {
                ilBuilder.Emit(OpCodes.Ldarg_0);
                ilBuilder.Emit(OpCodes.Ldflda, workloadDefaultValueHolderField);
                ilBuilder.EmitStloc(resultLocal);
            }
        }

        protected override void EmitActionBeforeCallOverride(ILGenerator ilBuilder)
        {
            /*
                <nothing>
                -or-
                // reference = ...
                IL_002a: ldloc.s 4
             */
            if (ActionKind != RunnableActionKind.Overhead)
            {
                ilBuilder.EmitLdloc(resultLocal);
            }
        }

        protected override void EmitActionAfterCallOverride(ILGenerator ilBuilder)
        {
            /*
                IL_0039: stloc.s 4
                -or-
                // reference = ...
                IL_003b: ldind.i4
                IL_003c: stind.i4
             */
            if (ActionKind == RunnableActionKind.Overhead)
            {
                ilBuilder.EmitStloc(resultLocal);
            }
            else
            {
                ilBuilder.EmitLdindStind(resultLocal.LocalType);
            }
        }

        protected override void EmitActionAfterLoopOverride(ILGenerator ilBuilder)
        {
            /*
                // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(value);
                IL_007a: ldloc.s 4
                IL_007c: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing<native int>(!!0)
                -or-
                // DeadCodeEliminationHelper.KeepAliveWithoutBoxing(ref reference);
                IL_0082: ldloc.s 4
                IL_0084: call void [BenchmarkDotNet]BenchmarkDotNet.Engines.DeadCodeEliminationHelper::KeepAliveWithoutBoxing<int32>(!!0&)
             */
            if (ActionKind == RunnableActionKind.Overhead)
                ilBuilder.EmitStaticCall(overheadKeepAliveWithoutBoxingMethod, resultLocal);
            else
                ilBuilder.EmitStaticCall(workloadKeepAliveWithoutBoxingMethod, resultLocal);
        }
    }
}